package ge.engine;


 
import ge.exceptions.FunctionNonSuported;
import ge.lowlevel.CollisionEngine;
import ge.lowlevel.DrawEngine;
import ge.lowlevel.EngineFactory;
import ge.lowlevel.EventEngine;
import ge.lowlevel.Interval;
import ge.lowlevel.SoundEngine;
import ge.lowlevel.UtilityEngine;
import ge.modules.events.CollisionModule;
import ge.modules.events.IOEventModule;

import com.google.gwt.user.client.Window;


public class Engine 
{ 
	private EngineFactory engines;
	
	private static int fps;
	static private Level currentLevel;
	static private Level newLevel;
	static private boolean safeload = false;
	private double lastTime = 0,lastUpdateTime = 0;
	static private Actor globalActor;
	
	//Information interesting for other classes
	static public double tick = 0;
	static public double updateTick;
	static private double volume = 1;
	static private Camera camera;
	/**
	 * 	Engine builder
	 * @param fps	frames per second
	 * @param factory Factory who establish what components are goint to be
	 * 	called by the engine in the low level depth.
	 */
	public Engine(EngineFactory factory, int fps)
	{
		Engine.fps = fps;
		newLevel = null; 
		engines = factory;
		engines.initEngines();
		camera = new Camera();
		globalActor = new Actor();
		safeload = false;
	}
	/**
	 * 
	 * @return The current level
	 */
	public static Level getCurrentLevel()
	{
		return currentLevel;
	}
	/**
	 * 	Makes te game start at the level indicated
	 * @param level Level to start with
	 */
	public void init(Level level)
	{
		newLevel = level;
		//Establecemos el bucle de update
		Interval loop = new Loop();
		
		try {
			UtilityEngine.get().setInterval(1000/fps, loop, true);
		} catch (FunctionNonSuported e) {
			e.printStackTrace();
		}
	}
	/**
	 * 	Indicates the engine to change the level in the next update
	 *  The old level won't be unloaded, and the new one won't too.
	 * @param level
	 */
	static public void setSafeLevel(Level level)
	{
		safeload = true;
		Engine.setLevel(level);
	}
	/**
	 * 	Indicates the engine to change the level in the next update
	 *  The old level will be unloaded, and the new one loaded.
	 * @param level
	 */
	static public void setLevel(Level level)
	{
		newLevel = level;
	}
	/**
	 * 	Measure the time spent since the last update
	 * @return	Time in milliseconds
	 */
	public static double tick()
	{
		return tick;
	}
	
	/**
	 * 		Measure actual frames per second
	 * 		It's just 1000/tick
	 * @return	 Frames Per second
	 */
	public static double fps()
	{
		 double d = 1000/tick;
		 return d;
	}
	public static double updateFps()
	{
		return 1000/updateTick;
	}
	/**
	 * 	Return the frame per seconds it's suppose to work the engine
	 * @return Frames per second.
	 */
	public static int getFps()
	{
		return fps;
	}
	/**
	 * 	Changes the level if a new level is set. Manages the load and unload level events
	 */
	private void getLevel()
	{
		if(newLevel != null)
		{
			if(currentLevel != null && !safeload)
				currentLevel.unload();	
			
			currentLevel = newLevel;
			newLevel = null;
			
			if(!safeload)
			{
				CollisionEngine.get().clear();
				EventEngine.get().clear();
				globalActor.add(new IOEventModule());
				globalActor.add(new CollisionModule());
				camera = new Camera();
				camera.enableCamera();
			}
			
			if(!safeload)
				currentLevel.load();
			lastTime = System.currentTimeMillis();
			 
		}
	}
	/**
	 * 	The global actor is a actor who is updated each up. 
	 *  Here you can add more elements who you want to live between levels,
	 *  global actor will be always updated.
	 * @return
	 */
	public static Actor getGlobalActor()
	{
		return globalActor;
	}
	/**
	 * 	
	 * @author Abel
	 *	This class represents the loop update and draw of the game, who
	 *	is repetad every 1000/fps indicated
	 */
	private class Loop extends Interval
	{
		/**
		 * 	This function executed the loop
		 */
		public void run()
		{
			
			try
			{
				getLevel(); //Initialitces lastTime when loads a new level
				double time = System.currentTimeMillis();
				tick = time-lastTime;
				//Log.write(time-lastUpdateTime+" : "+tick);
				if(time-lastUpdateTime > Engine.tick())
				{
					//Log.write("lo hace");
					update();
					double now = System.currentTimeMillis();
					updateTick =  now - lastUpdateTime;
					lastUpdateTime = now;
				}
				
				draw();
				lastTime = time;
			}
			catch(Exception ex)
			{
				Window.alert(ex.getMessage());
				ex.printStackTrace();
			}
		 
			
			
			
		}
		/**
		 *  Updates the level
		 */
		private void update()
		{
			
			camera.update();
			currentLevel.update();
			globalActor.update();
			Log.get().update();
		}
		/**
		 * 	Draw the level
		 */
		private void draw()
		{
			 DrawEngine.get().clear();
			 currentLevel.draw();
			 globalActor.draw();
			 Log.get().draw();
		}
	}
	/**
	 * @return Actual volume of the SoundEngine (0-1)
	 */
	static public double getVolume()
	{
		return volume;
	}
	/**
	 * 	Sets a new volume for the song
	 * @param newVolume double between 0 and 1
	 */
	static public void setVolume(double newVolume)
	{
		volume = newVolume;
		volume = Math.max(0, volume);
		volume = Math.min(1, volume);
		try {
			SoundEngine.get().setVolume(volume);
		} catch (FunctionNonSuported e) {
			e.printStackTrace();
		}
	}
	/**
	 * 	Changes the camera of the engine
	 * @param newCamera New object camera
	 */
	static public void setCamera(Camera newCamera)
	{
		camera.disableCamera();
		camera = newCamera;
		camera.enableCamera();
	}
	/**
	 * 	Returns the camera of the game
	 * @return a Camera object
	 */
	static public Camera getCamera()
	{
		return camera;
	}
	/**
	 * @return The width of the canvas
	 */
	static public double getCanvasWidth()
	{
		try {
			return DrawEngine.get().getWidth();
		} catch (FunctionNonSuported e) {
			e.printStackTrace();
		}
		return 0;
	}
	/**
	 * @return The height of the canvas
	 */
	static public double getCanvasHeigth()
	{
		try {
			return DrawEngine.get().getHeight();
		} catch (FunctionNonSuported e) {
			e.printStackTrace();
		}
		return 0;
	}
	
	static public int getNumberOfLayers()
	{
		try {
			return DrawEngine.get().getNumLayers();
		} catch (FunctionNonSuported e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return 1;
		}
	}
}
